// Copyright (C) 2008-2013 Conrad Sanderson
// Copyright (C) 2008-2013 NICTA (www.nicta.com.au)
// 
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.



//! \addtogroup arma_cmath
//! @{



//
// wrappers for isfinite


template<typename eT>
arma_inline
bool
arma_isfinite(eT val)
  {
  arma_ignore(val);
    
  return true;
  }



template<>
arma_inline
bool
arma_isfinite(float x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::isfinite(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::isfinite(x);
    }
  #elif defined(ARMA_HAVE_ISFINITE)
    {
    return (std::isfinite(x) != 0);
    }
  #else
    {
    const bool x_is_inf = ( (x == x) && ((x - x) != float(0)) );
    const bool x_is_nan = (x != x);
    
    return ( (x_is_inf == false) && (x_is_nan == false) );
    }
  #endif
  }



template<>
arma_inline
bool
arma_isfinite(double x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::isfinite(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::isfinite(x);
    }
  #elif defined(ARMA_HAVE_ISFINITE)
    {
    return (std::isfinite(x) != 0);
    }
  #else
    {
    const bool x_is_inf = ( (x == x) && ((x - x) != double(0)) );
    const bool x_is_nan = (x != x);
    
    return ( (x_is_inf == false) && (x_is_nan == false) );
    }
  #endif
  }



template<typename T>
arma_inline
bool
arma_isfinite(const std::complex<T>& x)
  {
  if( (arma_isfinite(x.real()) == false) || (arma_isfinite(x.imag()) == false) )
    {
    return false;
    }
  else
    {
    return true;
    }
  }



//
// wrappers for trigonometric functions
// 
// wherever possible, try to use C++11 or TR1 versions of the following functions:
// 
// complex acos
// complex asin
// complex atan
//
// real    acosh
// real    asinh
// real    atanh
//
// complex acosh
// complex asinh
// complex atanh
// 
// 
// if C++11 or TR1 are not available, we have rudimentary versions of:
// 
// real    acosh
// real    asinh
// real    atanh



template<typename T>
arma_inline
std::complex<T>
arma_acos(const std::complex<T>& x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::acos(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::acos(x);
    }
  #else
    {
    arma_stop("acos(): need C++11 compiler");
    
    return std::complex<T>(0);
    }
  #endif
  }



template<typename T>
arma_inline
std::complex<T>
arma_asin(const std::complex<T>& x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::asin(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::asin(x);
    }
  #else
    {
    arma_stop("asin(): need C++11 compiler");
    
    return std::complex<T>(0);
    }
  #endif
  }



template<typename T>
arma_inline
std::complex<T>
arma_atan(const std::complex<T>& x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::atan(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::atan(x);
    }
  #else
    {
    arma_stop("atan(): need C++11 compiler");
    
    return std::complex<T>(0);
    }
  #endif
  }



template<typename eT>
arma_inline
eT
arma_acosh(const eT x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::acosh(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::acosh(x);
    }
  #else
    {
    if(x >= eT(1))
      {
      // http://functions.wolfram.com/ElementaryFunctions/ArcCosh/02/
      return std::log( x + std::sqrt(x*x - eT(1)) );
      }
    else
      {
      if(std::numeric_limits<eT>::has_quiet_NaN == true)
        {
        return -(std::numeric_limits<eT>::quiet_NaN());
        }
      else
        {
        return eT(0);
        }
      }
    }
  #endif
  }



template<typename eT>
arma_inline
eT
arma_asinh(const eT x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::asinh(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::asinh(x);
    }
  #else
    {
    // http://functions.wolfram.com/ElementaryFunctions/ArcSinh/02/
    return std::log( x + std::sqrt(x*x + eT(1)) );
    }
  #endif
  }



template<typename eT>
arma_inline
eT
arma_atanh(const eT x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::atanh(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::atanh(x);
    }
  #else
    {
    if( (x >= eT(-1)) && (x <= eT(+1)) )
      {
      // http://functions.wolfram.com/ElementaryFunctions/ArcTanh/02/
      return std::log( ( eT(1)+x ) / ( eT(1)-x ) ) / eT(2);
      }
    else
      {
      if(std::numeric_limits<eT>::has_quiet_NaN == true)
        {
        return -(std::numeric_limits<eT>::quiet_NaN());
        }
      else
        {
        return eT(0);
        }
      }
    }
  #endif
  }



template<typename T>
arma_inline
std::complex<T>
arma_acosh(const std::complex<T>& x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::acosh(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::acosh(x);
    }
  #else
    {
    arma_stop("acosh(): need C++11 compiler");
    
    return std::complex<T>(0);
    }
  #endif
  }



template<typename T>
arma_inline
std::complex<T>
arma_asinh(const std::complex<T>& x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::asinh(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::asinh(x);
    }
  #else
    {
    arma_stop("asinh(): need C++11 compiler");
    
    return std::complex<T>(0);
    }
  #endif
  }



template<typename T>
arma_inline
std::complex<T>
arma_atanh(const std::complex<T>& x)
  {
  #if defined(ARMA_USE_CXX11)
    {
    return std::atanh(x);
    }
  #elif defined(ARMA_HAVE_TR1)
    {
    return std::tr1::atanh(x);
    }
  #else
    {
    arma_stop("atanh(): need C++11 compiler");
    
    return std::complex<T>(0);
    }
  #endif
  }



//! @}
